#include "stdafx.h"
#include "net/SocketAddress.hpp"
#include "net/DNS.hpp"
#include "net/HostEntry.hpp"
#include "stdlib/bugcheck.hpp"
using namespace JHCPP::stdlib;
#include "utils/numberformatter.hpp"
using JHCPP::utils::CNumberFormatter;
#include "utils/numberparser.hpp"
using JHCPP::utils::CNumberParser;

NET_NAMESPACE_BEGIN

struct AFLT
{
	bool operator () (const CIPAddress& a1, const CIPAddress& a2)
	{
		return a1.af() < a2.af();
	}
};


//
// CSocketAddress
//
CSocketAddress::CSocketAddress()
{
	newIPv4();
}

CSocketAddress::CSocketAddress(const CIPAddress& hostAddress, UInt16 portNumber)
{
	init(hostAddress, portNumber);
}

CSocketAddress::CSocketAddress(UInt16 portNumber)
{
	init(CIPAddress(), portNumber);
}

CSocketAddress::CSocketAddress(const std::string& hostAddress, UInt16 portNumber)
{
	init(hostAddress, portNumber);
}

CSocketAddress::CSocketAddress(const std::string& hostAddress, const std::string& portNumber)
{
	init(hostAddress, resolveService(portNumber));
}

CSocketAddress::CSocketAddress(const std::string& hostAndPort)
{
	jh_assert(!hostAndPort.empty());

	std::string host;
	std::string port;
	std::string::const_iterator it  = hostAndPort.begin();
	std::string::const_iterator end = hostAndPort.end();
	if (*it == '[')
	{
		++it;
		while (it != end && *it != ']') host += *it++;
		if (it == end) 
			throw InvalidArgumentException("Malformed IPv6 address");

		++it;
	}
	else
	{
		while (it != end && *it != ':') host += *it++;
	}
	if (it != end && *it == ':')
	{
		++it;
		while (it != end) port += *it++;
	}
	else 
		throw InvalidArgumentException("Missing port number");

	init(host, resolveService(port));
}

CSocketAddress::CSocketAddress(const CSocketAddress& socketAddress)
{
	if (socketAddress.family() == CIPAddress::IPv4)
		newIPv4(reinterpret_cast<const sockaddr_in*>(socketAddress.addr()));
	else
		newIPv6(reinterpret_cast<const sockaddr_in6*>(socketAddress.addr()));
}

CSocketAddress::CSocketAddress(const struct sockaddr* sockAddr, JH_SOCKETLEN length)
{
	if (length == sizeof(struct sockaddr_in))
		newIPv4(reinterpret_cast<const struct sockaddr_in*>(sockAddr));
#ifdef JH_HAVE_IPv6
	else if (length == sizeof(struct sockaddr_in6))
		newIPv6(reinterpret_cast<const struct sockaddr_in6*>(sockAddr));
#endif
	else 
		throw InvalidArgumentException("Invalid address length passed to SocketAddress()");
}

CSocketAddress::~CSocketAddress()
{
	destruct();
}

bool CSocketAddress::operator < (const CSocketAddress& socketAddress) const
{
	if (family() < socketAddress.family()) 
		return true;

	if (family() > socketAddress.family()) 
		return false;

	if (host() < socketAddress.host())
		return true;

	if (host() > socketAddress.host())
		return false;

	return (port() < socketAddress.port());
}

CSocketAddress& CSocketAddress::operator = (const CSocketAddress& socketAddress)
{
	if (&socketAddress != this)
	{
		destruct();
		if (socketAddress.family() == CIPAddress::IPv4)
			newIPv4(reinterpret_cast<const sockaddr_in*>(socketAddress.addr()));
		else
			newIPv6(reinterpret_cast<const sockaddr_in6*>(socketAddress.addr()));
	}
	return *this;
}

CIPAddress CSocketAddress::host() const
{
	return pImpl()->host();
}

UInt16 CSocketAddress::port() const
{
	return ntohs(pImpl()->port());
}

JH_SOCKETLEN CSocketAddress::length() const
{
	return pImpl()->length();
}

const struct sockaddr* CSocketAddress::addr() const
{
	return pImpl()->addr();
}

int CSocketAddress::af() const
{
	return pImpl()->af();
}

std::string CSocketAddress::toString() const
{
	std::string result;
#ifdef JH_HAVE_IPv6
	if (host().family() == CIPAddress::IPv6)
		result.append("[");
	result.append(host().toString());
	if (host().family() == CIPAddress::IPv6)
		result.append("]");
#endif
	result.append(":");
	CNumberFormatter::append(result, port());
	return result;
}

void CSocketAddress::init(const CIPAddress& hostAddress, UInt16 portNumber)
{
	if (hostAddress.family() == CIPAddress::IPv4)
		newIPv4(hostAddress, portNumber);
#ifdef JH_HAVE_IPv6
	else if (hostAddress.family() == CIPAddress::IPv6)
		newIPv6(hostAddress, portNumber);
#endif
	else 
		throw NotImplementedException("unsupported IP address family");
}

void CSocketAddress::init(const std::string& hostAddress, UInt16 portNumber)
{
	CIPAddress ip;
	if (CIPAddress::tryParse(hostAddress, ip))
	{
		init(ip, portNumber);
	}
	else
	{
		CHostEntry he = CDNS::hostByName(hostAddress);
		CHostEntry::AddressList addresses = he.addresses();
		if (addresses.size() > 0)
		{
		#ifdef JH_HAVE_IPv6
			// if we get both IPv4 and IPv6 addresses, prefer IPv4
			std::sort(addresses.begin(), addresses.end(), AFLT());
		#endif
			init(addresses[0], portNumber);
		}
		else 
			throw HostNotFoundException("No address found for host", hostAddress);
	}
}

UInt16 CSocketAddress::resolveService(const std::string& service)
{
	unsigned port;
	if (CNumberParser::tryParseUnsigned(service, port) && port <= 0xFFFF)
	{
		return (UInt16) port;
	}
	else
	{
		struct servent* se = getservbyname(service.c_str(), NULL);
		if (se)
			return ntohs(se->s_port);
		else
			throw ServiceNotFoundException(service);
	}
}

void CSocketAddress::destruct()
{
	pImpl()->~CSocketAddressImpl();
}

CSocketAddress::Ptr CSocketAddress::pImpl() const
{
	if (m_pImpl) 
		return m_pImpl;

	throw NullPointerException("Pointer to SocketAddress implementation is NULL.");
}

void CSocketAddress::newIPv4()
{
	m_pImpl = new CIPv4SocketAddressImpl;
}

void CSocketAddress::newIPv4(const sockaddr_in* sockAddr)
{
	m_pImpl = new CIPv4SocketAddressImpl(sockAddr);
}

void CSocketAddress::newIPv4(const CIPAddress& hostAddress, UInt16 portNumber)
{
	m_pImpl = new CIPv4SocketAddressImpl(hostAddress.addr(), htons(portNumber));
}

void CSocketAddress::newIPv6(const sockaddr_in6* sockAddr)
{
	m_pImpl = new CIPv6SocketAddressImpl(sockAddr);
}

void CSocketAddress::newIPv6(const CIPAddress& hostAddress, UInt16 portNumber)
{
	m_pImpl = new CIPv6SocketAddressImpl(hostAddress.addr(), htons(portNumber), hostAddress.scope());
}

CIPAddress::Family CSocketAddress::family() const
{
	return host().family();
}

bool CSocketAddress::operator == (const CSocketAddress& socketAddress) const
{
	return host() == socketAddress.host() && port() == socketAddress.port();
}

bool CSocketAddress::operator != (const CSocketAddress& socketAddress) const
{
	return host() != socketAddress.host() || port() != socketAddress.port();
}
NET_NAMESPACE_END
